# configure-mysql.pm
# Copyright (C) 2006  Stephane Alnet
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
# 

#
# For more information: http://carrierclass.net/
#
use strict; use warnings;

package CCNCore::Provision::Application::mysql;
use base qw(CCNCore::Provision::Database);
use CCNCore::Scripting;
use CCN::Scripting::sql;

sub anchor_system
{
    my $self = shift;
    # Subclass
}

sub run()
{
    my $self = shift;
    return if $self->p->force_manager;

    $self->PACKAGE ('mysql-server');
    $self->PACKAGE ('libdbd-mysql-perl');

    $self->CHECKFOR ('/etc/init.d/mysql');
    $self->CHECKFOR ('/etc/mysql/my.cnf');
    $self->CHECKFOR ('/usr/sbin/mysqld');

    $self->CHECKFOR ('/usr/bin/mysqldump');

    # Should MySQL be enabled?
    return $self->disable('mysql')
        unless defined $self->p->{db_member};

    $self->enable('mysql');

    # ****** FOR CCN 2.0 ***********
    # we will use master-master only. The primary and secondary nodes are writers, the other nodes (if any) are
    # readers.
    # It appears ndbcluster works (tested OK) but there are strict limitations when it is used with multiple
    # MySQL instances (locks, etc.) that don't make it an interesting option for now. Its behavior during system
    # crash or loss of connection has also been eratic at best, even in the lab.

    # Master-master replication model
    {
        my $SALT = $self->system_salt;

        my $my_pk = $self->p->server_pk;
        # For master-master, assume two servers.
        my $NB_OF_SERVERS = 2;

        my $am_primary = 0;

        # Lowest pk is the primary.
        my @my_db_servers = $self->sql_get_array('SELECT id FROM server WHERE db_member = ? ORDER BY id ASC',$self->p->{db_member});
        
        my $DB_MASTER;
        my $DB_SERVER_ID;
        my $DB_MASTER_ID;
        my $READ_ONLY;

        # Primary Master
        if( $my_db_servers[0] eq $my_pk )
        {
            $am_primary = 1;
            $DB_MASTER = $my_db_servers[1];
            $DB_SERVER_ID = 1;
            $DB_MASTER_ID = 2;
            $READ_ONLY = '';
        }
        # Secondary Master
        elsif( $my_db_servers[1] eq $my_pk )
        {
            $DB_MASTER = $my_db_servers[0];
            $DB_SERVER_ID = 2;
            $DB_MASTER_ID = 1;
            $READ_ONLY = '';
        }
        # Unsupported: read-only server
        else
        {
            $self->warn ('Read-only database servers are not a supported configuration.');
            $DB_MASTER = $my_db_servers[0];
            $DB_SERVER_ID = -1;
            $READ_ONLY = 'read-only';
        }

        my $servers_init = '';
        my $MASTER_MASTER;

        # At least two servers: enable replication
        if( $#my_db_servers >= 1 )
        {
            return $self->error ("Could not allocate a database master.") unless defined $DB_MASTER;

            my $DB_SERVER_IP = $self->primary_ip($my_pk);
            my $DB_MASTER_IP = $self->primary_ip($DB_MASTER);
            # The password we use to authenticate with our master.
            my $DB_MASTER_PASSWORD = $self->make_password($self->p->{db_member},${DB_MASTER},${SALT},$self->p->server_pk,'db');

            # Enable replication for the other servers in the cluster.
            # if( $am_primary )
            if( $DB_SERVER_ID > 0 )
            {
                foreach(@my_db_servers)
                {
                    next if $_ eq $my_pk;
                    my $ip = $self->primary_ip($_);
                    # The password this slave uses to authenticate with us.
                    my $password = $self->make_password($self->p->{db_member},$self->p->server_pk,${SALT},$_,'db');
                    $servers_init .=
                        SQL 'mysql', "GRANT REPLICATION SLAVE ON *.* TO 'replication'\@$_ IDENTIFIED BY '$password';";
                    $servers_init .=
                        SQL 'mysql', "GRANT REPLICATION SLAVE ON *.* TO 'replication'\@$ip IDENTIFIED BY '$password';";
                } # foreach
            } #

            my $THIS_SERVER = $self$self->p->make_server_name($self->p->server_pk);

            $MASTER_MASTER = <<"EOT";
# ID for _this_ server (this is independent from the NDB ids but could re-use the same values)
server-id       = ${DB_SERVER_ID}

# DB_MASTER refers to the master server used:
#   for the primary, it is the secondary
#   for the secondary, it is the primary
#   for the other servers, it is any of the primary or the secondary (maybe we can "help" the system decide, but
#      maybe it's as simple to say that there should be two clusters in that case)
#   .. remember we are talking about the database cluster, so we can have a two-hosts database cluster serving more
#      than two callproc or proxy servers in their respective clusters.
#   If we lose either the primary or the secondary, the third etc. nodes will have a split view of the database.
#   Another option is to have the servers in the DB cluster configured in a ring. However I'm not sure what happens when the
#   ring is broken (since the head will not see the changes other nodes will see).

master-host                 = ${DB_MASTER_IP}
master-user                 = replication
master-password             = ${DB_MASTER_PASSWORD}
master-port                 = 3306
relay-log                   = mysql-${DB_MASTER_ID}-relay-bin
replicate-same-server-id    = 0
auto_increment_increment    = ${NB_OF_SERVERS}
auto_increment_offset       = ${DB_SERVER_ID}
report-host                 = ${THIS_SERVER}

log-slave-updates
# This causes data corruption.
# slave_skip_errors   = all

# FOR READ-ONLY SLAVES
${READ_ONLY}

EOT
        }
        # Only one server: do not enable replication
        else
        {
            $MASTER_MASTER = '';
            $self->warn ('Only one database server is defined. Replication not enabled.');
        }

        foreach(CCNCore::Home::config.'/local/bin/sql-servers-init.sh')
        {
            CAT $_, $servers_init;
            IF_CHANGED $_ => "/bin/sh $_";
        }

        my $DB_SERVER_IP = $self->get_replication_ip_of($self->p->server_pk);

        my $DB_NAMES = join('',map { "binlog-do-db        = $_\n" } ($self->p->get_db_names));
        # $DB_NAMES = "binlog-do-db        = \n"
        #     if $DB_NAMES eq '';

        CAT '/etc/mysql/my.cnf', <<"EOT";
[client]
port        = 3306
socket      = /var/run/mysqld/mysqld.sock

[mysqld_safe]
socket      = /var/run/mysqld/mysqld.sock
nice        = 0
err-log     = /var/log/mysql/messages

[mysqld]
sql-mode    = "ANSI,STRICT_TRANS_TABLES,TRADITIONAL"
user        = mysql
pid-file    = /var/run/mysqld/mysqld.pid
socket      = /var/run/mysqld/mysqld.sock
port        = 3306
basedir     = /usr
datadir     = /var/lib/mysql
tmpdir      = /tmp
language    = /usr/share/mysql/english
skip-external-locking
#old_passwords   = 1
#secure-auth = 1
#skip-show-database = 1
skip-name-resolve
#safe-user-create = 1
#local-infile = 0
#
bind-address = ${DB_SERVER_IP}
key_buffer          = 16M
max_allowed_packet  = 16M
thread_stack        = 128K
thread_cache_size   = 8
query_cache_limit   = 1048576
query_cache_size    = 16777216
query_cache_type    = 1
# ONLY LOG QUERIES FOR TROUBLESHOOTING
# log        = /var/log/mysql/queries
# ---------------
log-slow-queries   = /var/log/mysql/queries.slow

# *** START MASTER-MASTER REPLICATION ***

${MASTER_MASTER}

${DB_NAMES}
binlog-ignore-db    = mysql

# *** END MASTER-MASTER REPLICATION ***

sync_binlog         = 1
log-bin             = mysql-bin
innodb_flush_log_at_trx_commit = 1
expire-logs-days    = 0
max_binlog_size     = 104857600


skip-bdb
skip-innodb

# ssl-ca=/etc/mysql/cacert.pem
# ssl-cert=/etc/mysql/server-cert.pem
# ssl-key=/etc/mysql/server-key.pem

[mysql.server]
user=mysql
basedir=/var/lib

[mysqldump]
quick
quote-names
max_allowed_packet  = 16M

[mysql]
no-auto-rehash # faster start of mysql but no tab completition

[isamchk]
key_buffer          = 16M

# [MYSQL_CLUSTER]
# ndb-connectstring=127.0.0.1

EOT

    }

    COMMIT_MODULE(ref($self));
    $self->anchor_system();
}

sub commit($)
{
    my $self = shift;
    my $c = shift;

    $c->run_if_changed('/etc/mysql/my.cnf','/etc/init.d/mysql stop && /etc/init.d/mysql start');
    $c->run_if_changed('/etc/mysql/my.cnf','mysqladmin flush-hosts');

    if( $self->bootstrapping or $self->upgrading )
    {
        CCN::Scripting::exec_command 'mysqladmin flush-hosts || /etc/init.d/mysql restart';
    }
    
    use CCNCore::Scripting;
    CCNCore::Scripting::CRONTAB q(0 3 * * sun  mysql -uroot -e "PURGE MASTER LOGS BEFORE DATE_SUB( NOW( ), INTERVAL 31 DAY);");
    CCNCore::Scripting::CRONTAB q(40 3 * * * /usr/bin/mysqldump --all-databases > ).CCNCore::Home::config.q(/local/mysqldump.all);

    # .force-sql-bootstrap notifies startup_mysql that it should re-run the
    # master-slave synchronization process (normally because a server got added or removed
    # from the cluster).
    use CCNCore::Home;
    unlink CCNCore::Home::config.'/local/.force-sql-bootstrap';
    $c->run_if_changed('/etc/mysql/my.cnf','echo > '.CCNCore::Home::config.'/local/.force-sql-bootstrap');
}

1;